use std::collections::HashMap;

use apikit::{
    ApiErrors, StatusCode,
    doc::{
        ApiParamPart, ApiRequest, ApiRequestExtractor, ApiResponse, ApiResponseExtractor,
        ApiTypeId, ApiTypeInfo, ApiWebsocketMessage, CollectApiTypeInfo, IntoApiTypeId,
    },
};
use glass::{ConvertError, Object, Reflect, Value};

use crate::{
    dto::{FileContent, FileItem, ResponseBody, SseEvent, WsMessage},
    error::{BadRequest, EntityError},
    param::{
        __TypeMapper, Form, Header, Json, Multipart, Path, Query, SseResponse, StreamResult,
        WsResponse, WsUpgrade,
    },
};

impl<T: Reflect> ApiRequestExtractor<Path<T>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let type_info = T::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);

        let mut errors = EntityError::api_errors();
        errors.extend(BadRequest::api_errors());
        api_request.parts.push(ApiParamPart::Path {
            params: type_id.into_api_type_id(),
            errors,
        });
    }
}

impl<T: Reflect> ApiRequestExtractor<Query<T>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let type_info = T::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        let mut errors = EntityError::api_errors();
        errors.extend(BadRequest::api_errors());
        api_request.parts.push(ApiParamPart::Query {
            params: type_id.into_api_type_id(),
            errors,
        });
    }
}

impl<T: Reflect> ApiRequestExtractor<Header<T>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let type_info = T::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        let mut errors = EntityError::api_errors();
        errors.extend(BadRequest::api_errors());
        api_request.parts.push(ApiParamPart::Header {
            params: type_id.into_api_type_id(),
            errors,
        });
    }
}

impl<T: Reflect> ApiRequestExtractor<Json<T>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let type_info = T::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        let mut errors = EntityError::api_errors();
        errors.extend(BadRequest::api_errors());
        api_request.parts.push(ApiParamPart::Body {
            params: type_id.into_api_type_id(),
            errors,
        });
        api_request.content_type = Some(mime::APPLICATION_JSON.to_string());
    }
}

impl<T: Reflect> ApiRequestExtractor<Form<T>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let type_info = T::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        let mut errors = EntityError::api_errors();
        errors.extend(BadRequest::api_errors());
        api_request.parts.push(ApiParamPart::Body {
            params: type_id.into_api_type_id(),
            errors,
        });
        api_request.content_type = Some(mime::FORM_DATA.to_string());
    }
}

impl<T: Reflect> ApiRequestExtractor<Multipart<T>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let type_info = <T as Reflect>::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        let mut errors = EntityError::api_errors();
        errors.extend(BadRequest::api_errors());
        api_request.parts.push(ApiParamPart::Body {
            params: type_id.into_api_type_id(),
            errors,
        });
        api_request.content_type = Some(mime::MULTIPART_FORM_DATA.to_string());
    }
}

impl<T: Reflect, E: ApiErrors> ApiResponseExtractor<Result<T, E>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_response: &mut ApiResponse) {
        let type_info = ResponseBody::<T>::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        api_response.body = Some(type_id.into_api_type_id());
        api_response.content_type = Some(mime::APPLICATION_JSON.to_string());
        api_response.errors = E::api_errors();
    }
}

impl<S: Reflect, R: Reflect, SC: Reflect, RC: Reflect> ApiRequestExtractor<WsUpgrade<S, R, SC, RC>>
    for __TypeMapper
{
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_request: &mut ApiRequest) {
        let s_type_info = WsMessage::<S, SC>::type_info();
        let s_type_id = s_type_info.type_id;
        s_type_info.collect(types);

        let r_type_info = WsMessage::<R, RC>::type_info();
        let r_type_id = r_type_info.type_id;
        r_type_info.collect(types);

        let errors = vec![StatusCode::UPGRADE_REQUIRED.into()];
        api_request.parts.push(ApiParamPart::WsHandler {
            send: ApiWebsocketMessage {
                message: Some(s_type_id.into_api_type_id()),
                content_type: Some(mime::APPLICATION_JSON.to_string()),
            },
            receive: ApiWebsocketMessage {
                message: Some(r_type_id.into_api_type_id()),
                content_type: Some(mime::APPLICATION_JSON.to_string()),
            },
            errors,
        });
    }
}

impl<E: ApiErrors> ApiResponseExtractor<Result<WsResponse, E>> for __TypeMapper {
    fn extract(_types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_response: &mut ApiResponse) {
        api_response.errors = E::api_errors();
    }
}

impl<T: Reflect, E: ApiErrors> ApiResponseExtractor<Result<SseResponse<T>, E>> for __TypeMapper {
    fn extract(types: &mut HashMap<ApiTypeId, ApiTypeInfo>, api_response: &mut ApiResponse) {
        let type_info = SseEvent::<T>::type_info();
        let type_id = type_info.type_id;
        type_info.collect(types);
        api_response.body = Some(type_id.into_api_type_id());
        api_response.content_type = Some(mime::EVENT_STREAM.to_string());
        api_response.errors = E::api_errors();
    }
}

impl ApiResponseExtractor<StreamResult> for __TypeMapper {
    fn extract(_types: &mut HashMap<ApiTypeId, ApiTypeInfo>, _api_response: &mut ApiResponse) {}
}

impl TryInto<Object> for FileItem {
    type Error = ConvertError;

    fn try_into(self) -> Result<Object, Self::Error> {
        Ok(Object {
            type_info_fn: Self::type_info,
            value: Value::File(self.content.bytes.to_vec()),
        })
    }
}

impl TryFrom<Value> for FileItem {
    type Error = ConvertError;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        let actual_kind = value.to_string();
        if let Value::File(bytes) = value {
            return Ok(Self {
                content: FileContent {
                    bytes: bytes.into(),
                },
                file_name: None,
                content_type: None,
            });
        }

        Err(ConvertError::type_mismatch("file", &actual_kind))
    }
}

impl Reflect for FileItem {
    fn kind() -> glass::TypeKind {
        glass::TypeKind::File
    }
}
